/*
* Sun Public License Notice
*
* The contents of this file are subject to the Sun Public License
* Version 1.0 (the "License"). You may not use this file except in
* compliance with the License. A copy of the License is available at
* http://www.sun.com/
*
* The Original Code is Forte for Java, Community Edition. The Initial
* Developer of the Original Code is Sun Microsystems, Inc. Portions
* Copyright 1997-2000 Sun Microsystems, Inc. All Rights Reserved.
*/
package org.openide.filesystems;
import java.io.*;
import java.util.Enumeration;
import java.util.Hashtable;
import org.openide.execution.NbfsURLConnection;
import org.openide.util.enum.FilterEnumeration;
import org.openide.util.enum.QueueEnumeration;
/** This is the base for all implementations of file objects on a file system.
* Provides basic information about the object (its name, parent,
* whether it exists, etc.) and operations on it (move, delete, etc.).
*
* @author Jaroslav Tulach, Petr Hamernik, Ian Formanek
*/
public abstract class FileObject extends Object implements java.io.Serializable {
/** generated Serialized Version UID */
static final long serialVersionUID = 85305031923497718L;
/** Get the name without extension of this file.
*
* @return name of the file (in its enclosing folder)
*/
public abstract String getName ();
/** Get the extension of this file.
* This is the string after the last dot of the full name, if any.
*
* @return extension of the file (if any) or empty string if there is none
*/
public abstract String getExt ();
/** Renames this file (or folder).
* Both the new basename and new extension should be specified.
* <p>
* Note that using this call, it is currently only possible to rename <em>within</em>
* a parent folder, and not to do moves <em>across</em> folders.
* Conversely, implementing file systems need only implement "simple" renames.
* If you wish to move a file across folders, you should call {@link FileUtil#moveFile}.
* @param lock File must be locked before renaming.
* @param name new basename of file
* @param ext new extension of file (ignored for folders)
*/
public abstract void rename(FileLock lock, String name, String ext) throws IOException;
/** Copies this file. This allows the filesystem to perform any additional
* operation associated with the copy. But the default implementation is simple
* copy of the file and its attributes
*
* @param target target folder to move this file to
* @param name new basename of file
* @param ext new extension of file (ignored for folders)
* @return the newly created file object representing the moved file
*/
public FileObject copy (FileObject target, String name, String ext)
throws IOException {
FileObject dest = FileUtil.copyFileImpl (this, target, name, ext);
return dest;
}
/** Moves this file. This allows the filesystem to perform any additional
* operation associated with the move. But the default implementation is encapsulated
* as copy and delete.
*
* @param lock File must be locked before renaming.
* @param target target folder to move this file to
* @param name new basename of file
* @param ext new extension of file (ignored for folders)
* @return the newly created file object representing the moved file
*/
public FileObject move (FileLock lock, FileObject target, String name, String ext)
throws IOException {
if (getParent ().equals (target)) {
// it is possible to do only rename
rename (lock, name, ext);
return this;
} else {
// have to do copy
FileObject dest = copy (target, name, ext);
delete(lock);
return dest;
}
}
/** Get fully-qualified filename. Does so by walking through all folders
* to the root of the file system. Separates files with provided <code>separatorChar</code>.
* The extension, if present, is separated from the basename with <code>extSepChar</code>.
*
* @param separatorChar char to separate folders and files
* @param extSepChar char to separate extension
* @return the fully-qualified filename
*/
public String getPackageNameExt (char separatorChar, char extSepChar) {
String pn = getPackageName (separatorChar);
String ext = getExt ();
if (!ext.equals ("")) { // NOI18N
// add extension
return pn + extSepChar + ext;
} else {
// without extension
return pn;
}
}
/** Get fully-qualified filename, but without extension.
* Like {@link #getPackageNameExt} but omits the extension.
* @param separatorChar char to separate folders and files
* @return the fully-qualified filename
*/
public String getPackageName (char separatorChar) {
StringBuffer sb = new StringBuffer ();
constructName (sb, separatorChar);
return sb.toString ();
}
/** Constructs path of file.
* @param sb string buffer
* @param sepChar separator character
*/
private void constructName (StringBuffer sb, char sepChar) {
FileObject parent = getParent ();
if ((parent != null) && !parent.isRoot ()) {
parent.constructName (sb, sepChar);
sb.append (sepChar);
}
sb.append (getName ());
}
/** Get the file system containing this file.
* <p>
* Note that it may be possible for a stale file object to exist which refers to a now-defunct file system.
* If this is the case, this method will throw an exception.
* @return the file system
* @exception FileStateInvalidException if the reference to the file
* system has been lost (e.g., if the file system was deleted)
*/
public abstract FileSystem getFileSystem () throws FileStateInvalidException;
/** Get parent folder.
* The returned object will satisfy {@link #isFolder}.
*
* @return the parent folder or <code>null</code> if this object {@link #isRoot}.
*/
public abstract FileObject getParent ();
/** Test whether this object is a folder.
* @return true if the file object is a folder (i.e., can have children)
*/
public abstract boolean isFolder ();
/**
* Get last modification time.
* @return the date
*/
public abstract java.util.Date lastModified();
/** Test whether this object is the root folder.
* The root should always be a folder.
* @return true if the object is the root of a file system
*/
public abstract boolean isRoot ();
/** Test whether this object is a data object.
* This is exclusive with {@link #isFolder}.
* @return true if the file object represents data (i.e., can be read and written)
*/
public abstract boolean isData ();
/** Test whether the file is valid.
*
* @see FileSystem#isValid
*
* @return true if the file object is valid
*/
public abstract boolean isValid ();
/** Test whether there is a file with the same basename and only a changed extension in the same folder.
* The default implementation asks this file's parent using {@link #getFileObject(String name, String ext)}.
*
* @param ext the alternate extension
* @return true if there is such a file
*/
public boolean existsExt (String ext) {
FileObject parent = getParent ();
return parent != null && parent.getFileObject (getName (), ext) != null;
}
/** Delete this file. If the file is a folder and it is not empty then
* all of its contents are also recursively deleted.
*
* @param lock the lock obtained by a call to {@link #lock}
* @exception IOException if the file could not be deleted
*/
public abstract void delete (FileLock lock) throws IOException;
/** Get the file attribute with the specified name.
* @param attrName name of the attribute
* @return appropriate (serializable) value or <CODE>null</CODE> if the attribute is unset (or could not be properly restored for some reason)
*/
abstract public Object getAttribute(String attrName);
/** Set the file attribute with the specified name.
* @param attrName name of the attribute
* @param value new value or <code>null</code> to clear the attribute. Must be serializable, although particular file systems may or may not use serialization to store attribute values.
* @exception IOException if the attribute cannot be set. If serialization is used to store it, this may in fact be a subclass such as {@link NotSerializableException}.
*/
abstract public void setAttribute(String attrName, Object value) throws IOException;
/** Get all file attribute names for this file.
* @return enumeration of keys (as strings)
*/
abstract public Enumeration getAttributes();
/** Test whether this file has the specified extension.
* @param ext the extension the file should have
* @return true if the text after the last period (<code>.</code>) is equal to the given extension
*/
public final boolean hasExt (String ext) {
return getExt ().equals (ext);
}
/** Add new listener to this object.
* @param l the listener
*/
public abstract void addFileChangeListener (FileChangeListener fcl);
/** Remove listener from this object.
* @param l the listener
*/
public abstract void removeFileChangeListener (FileChangeListener fcl);
/** Fire data creation event.
* @param en enumeration of {@link FileChangeListener}s that should receive the event
* @param fe the event to fire in this object
*/
protected void fireFileDataCreatedEvent (
final Enumeration en, final FileEvent fe
) {
putEventDispatcher (new FileSystem.EventDispatcher () {
public void dispatch () {
while (en.hasMoreElements ()) {
FileChangeListener fcl = (FileChangeListener)en.nextElement ();
fcl.fileDataCreated (fe);
}
}
});
}
/** Fire folder creation event.
* @param en enumeration of {@link FileChangeListener}s that should receive the event
* @param fe the event to fire in this object
*/
protected void fireFileFolderCreatedEvent (
final Enumeration en, final FileEvent fe
) {
putEventDispatcher (new FileSystem.EventDispatcher () {
public void dispatch () {
while (en.hasMoreElements ()) {
FileChangeListener fcl = (FileChangeListener)en.nextElement ();
fcl.fileFolderCreated (fe);
}
}
});
}
/** Fire file change event.
* @param en enumeration of {@link FileChangeListener}s that should receive the event
* @param fe the event to fire in this object
*/
protected void fireFileChangedEvent (
final Enumeration en, final FileEvent fe
) {
putEventDispatcher (new FileSystem.EventDispatcher () {
public void dispatch () {
while (en.hasMoreElements ()) {
FileChangeListener fcl = (FileChangeListener)en.nextElement ();
fcl.fileChanged (fe);
}
}
});
}
/** Fire file deletion event.
* @param en enumeration of {@link FileChangeListener}s that should receive the event
* @param fe the event to fire in this object
*/
protected void fireFileDeletedEvent (
final Enumeration en, final FileEvent fe
) {
putEventDispatcher (new FileSystem.EventDispatcher () {
public void dispatch () {
while (en.hasMoreElements ()) {
FileChangeListener fcl = (FileChangeListener)en.nextElement ();
fcl.fileDeleted (fe);
}
}
});
}
/** Fire file attribute change event.
* @param en enumeration of {@link FileChangeListener}s that should receive the event
* @param fe the event to fire in this object
*/
protected void fireFileAttributeChangedEvent (
final Enumeration en, final FileAttributeEvent fe
) {
putEventDispatcher (new FileSystem.EventDispatcher () {
public void dispatch () {
while (en.hasMoreElements ()) {
FileChangeListener fcl = (FileChangeListener)en.nextElement ();
fcl.fileAttributeChanged (fe);
}
}
});
}
/** Fire file rename event.
* @param en enumeration of {@link FileChangeListener}s that should receive the event
* @param fe the event to fire in this object
*/
protected void fireFileRenamedEvent (
final Enumeration en, final FileRenameEvent fe
) {
putEventDispatcher (new FileSystem.EventDispatcher () {
public void dispatch () {
while (en.hasMoreElements ()) {
FileChangeListener fcl = (FileChangeListener)en.nextElement ();
fcl.fileRenamed (fe);
}
}
});
}
/** Puts the dispatch event into the file system.
*/
private final void putEventDispatcher (FileSystem.EventDispatcher d) {
try {
FileSystem fs = getFileSystem ();
fs.putEventDispatcher (d);
} catch (FileStateInvalidException ex) {
// no file system, no notification
}
}
/** Get the MIME type of this file.
* The MIME type identifies the type of the file's contents and should be used in the same way as in the <B>Java
* Activation Framework</B> or in the {@link java.awt.datatransfer} package.
* <P>
* The default implementation calls {@link FileUtil#getMIMEType}.
* (As a fallback return value, <code>content/unknown</code> is used.)
* @return the MIME type textual representation, e.g. <code>"text/plain"</code>; never <code>null</code>
*/
public String getMIMEType () {
String type = FileUtil.getMIMEType (getExt ());
return type == null ? "content/unknown" : type; // NOI18N
}
/** Get the size of the file.
* @return the size of the file in bytes or zero if the file does not contain data (does not
* exist or is a folder).
*/
public abstract long getSize ();
/** Get input stream.
* @return an input stream to read the contents of this file
* @exception FileNotFoundException if the file does not exists or is invalid
*/
public abstract InputStream getInputStream () throws java.io.FileNotFoundException;
/** Get output stream.
* @param lock the lock that belongs to this file (obtained by a call to
* {@link #lock})
* @return output stream to overwrite the contents of this file
* @exception IOException if an error occures (the file is invalid, etc.)
*/
public abstract OutputStream getOutputStream (FileLock lock)
throws java.io.IOException;
/** Lock this file.
* @return lock that can be used to perform various modifications on the file
* @throws FileAlreadyLockedException if the file is already locked
*/
public abstract FileLock lock () throws IOException;
// [???] Implicit file state is important.
/** Indicate whether this file is important from a user perspective.
* This method allows a file system to distingush between important and
* unimportant files when this distinction is possible.
* <P>
* <em>For example:</em> Java sources have important <code>.java</code> files and
* unimportant <code>.class</code> files. If the file system provides
* an "archive" feature it should archive only <code>.java</code> files.
* @param b true if the file should be considered important
*/
public abstract void setImportant (boolean b);
/** Get all children of this folder (files and subfolders). If the file does not have children
* (does not exist or is not a folder) then an empty array should be returned. No particular order is assumed.
*
* @return array of direct children
* @see #getChildren(boolean)
* @see #getFolders
* @see #getData
*/
public abstract FileObject[] getChildren ();
/** Enumerate all children of this folder. If the children should be enumerated
* recursively, first all direct children are listed; then children of direct subfolders; and so on.
*
* @param rec whether to enumerate recursively
* @return enumeration of type <code>FileObject</code>
*/
public Enumeration getChildren (final boolean rec) {
QueueEnumeration en = new QueueEnumeration () {
/** @param o processes object by adding its children to the queue */
public void process (Object o) {
FileObject fo = (FileObject)o;
if (rec && fo.isFolder ()) {
addChildrenToEnum (this, fo.getChildren ());
}
}
};
addChildrenToEnum (en, getChildren ());
return en;
}
/** Puts children into QueueEnumeration.
* @param en the queue enumeration to add children to
* @param list array of file objects
*/
static void addChildrenToEnum (QueueEnumeration en, FileObject[] list) {
for (int i = 0; i < list.length; i++) {
en.put (list[i]);
}
}
/** Enumerate the subfolders of this folder.
* @param rec whether to recursively list subfolders
* @return enumeration of type <code>FileObject</code> (satisfying {@link #isFolder})
*/
public Enumeration getFolders (boolean rec) {
return new org.openide.util.enum.FilterEnumeration (getChildren (rec)) {
/** @return true if the object is of type FileFolder */
protected boolean accept (Object o) {
return ((FileObject)o).isFolder ();
}
};
}
/** Enumerate all data files in this folder.
* @param rec whether to recursively search subfolders
* @return enumeration of type <code>FileObject</code> (satisfying {@link #isData})
*/
public Enumeration getData (boolean rec) {
return new org.openide.util.enum.FilterEnumeration (getChildren (rec)) {
/** @return true if the object is of type FileFolder */
protected boolean accept (Object o) {
return ((FileObject)o).isData ();
}
};
}
/** Retrieve file contained in this folder by name.
* <em>Note</em> that no file is created on disk.
* @param name basename of the file (in this folder)
* @param ext extension of the file; <CODE>null</CODE> or <code>""</code>
* if the file should have no extension
* @return the object representing this file or <CODE>null</CODE> if the file
* does not exist
* @exception IllegalArgumentException if <code>this</code> is not a folder
*/
public abstract FileObject getFileObject (String name, String ext);
/** Retrieve file contained in this folder by name (no extension).
* <em>Note</em> that no file is created on disk.
* @param name basename of the file (in this folder)
* @return the object representing this file or <CODE>null</CODE> if the file
* does not exist
* @exception IllegalArgumentException if <code>this</code> is not a folder
*/
public final FileObject getFileObject (String name) {
return getFileObject (name, null);
}
/** Create a new folder below this one with the specified name. Fires
* <code>fileCreated</code> event.
*
* @param name the name of folder to create (without extension)
* @return the new folder
* @exception IOException if the folder cannot be created (e.g. already exists)
*/
public abstract FileObject createFolder (String name) throws IOException;
/** Create new data file in this folder with the specified name. Fires
* <code>fileCreated</code> event.
*
* @param name the name of data object to create (should not contain a period)
* @param ext the extension of the file (or <code>null</code> or <code>""</code>)
*
* @return the new data file object
* @exception IOException if the file cannot be created (e.g. already exists)
*/
public abstract FileObject createData (String name, String ext) throws IOException;
/** Test whether this file can be written to or not.
* @return <CODE>true</CODE> if file is read-only
*/
public abstract boolean isReadOnly ();
/** Should check for external modifications. For folders it should reread
* the content of disk, for data file it should check for the last
* time the file has been modified.
*
* @param expected should the file events be marked as expected change or not?
* @see FileEvent#isExpected
*/
public void refresh (boolean expected) {
}
/** Should check for external modifications. For folders it should reread
* the content of disk, for data file it should check for the last
* time the file has been modified.
* <P>
* The file events are marked as unexpected.
*/
public void refresh () {
refresh (false);
}
/** Get URL that can be used to access this file.
* The URL is only usable within the IDE as it uses a special protocol handler.
* {@link org.openide.execution.NbClassLoader} must be installed (this is done automatically in the {@link Repository} constructor).
* @return URL of this file object
* @exception FileStateInvalidException if the file is not valid
*/
public final java.net.URL getURL() throws FileStateInvalidException {
return NbfsURLConnection.encodeFileObject (this);
}
}
/*
* Log
* 24 Gandalf 1.23 1/14/00 Jaroslav Tulach refresh (expected)
* 23 Gandalf 1.22 1/12/00 Ian Formanek NOI18N
* 22 Gandalf 1.21 10/22/99 Ian Formanek NO SEMANTIC CHANGE - Sun
* Microsystems Copyright in File Comment
* 21 Gandalf 1.20 10/1/99 Jaroslav Tulach FileObject.move &
* FileObject.copy
* 20 Gandalf 1.19 8/30/99 Jesse Glick [JavaDoc]
* 19 Gandalf 1.18 6/8/99 Ian Formanek ---- Package Change To
* org.openide ----
* 18 Gandalf 1.17 6/1/99 Jaroslav Tulach Synch on atomic actions
* 17 Gandalf 1.16 3/26/99 Jesse Glick Same javadoc as before,
* only without the huge block of ASCII nuls this time, ^%*%$*^&.
* 16 Gandalf 1.15 3/26/99 Jesse Glick StarTeam bullshit, don't
* ask.
* 15 Gandalf 1.14 3/26/99 Jesse Glick [JavaDoc]
* 14 Gandalf 1.13 3/26/99 Jaroslav Tulach
* 13 Gandalf 1.12 3/13/99 Jaroslav Tulach FileSystem.Status &
* lastModified
* 12 Gandalf 1.11 3/11/99 Jesse Glick [JavaDoc]
* 11 Gandalf 1.10 3/1/99 Jesse Glick [JavaDoc]
* 10 Gandalf 1.9 2/11/99 Ian Formanek Renamed FileSystemPool ->
* Repository
* 9 Gandalf 1.8 2/4/99 Petr Hamernik setting of extended file
* attributes doesn't require FileLock
* 8 Gandalf 1.7 2/2/99 Jesse Glick [JavaDoc]
* 7 Gandalf 1.6 2/1/99 Jesse Glick [JavaDoc]
* 6 Gandalf 1.5 2/1/99 Jesse Glick [JavaDoc]
* 5 Gandalf 1.4 1/12/99 Jaroslav Tulach
* 4 Gandalf 1.3 1/11/99 Jaroslav Tulach NbClassLoader extends
* URLClassLoader
* 3 Gandalf 1.2 1/6/99 Jaroslav Tulach Change of package of
* DataObject
* 2 Gandalf 1.1 1/6/99 Ales Novak
* 1 Gandalf 1.0 1/5/99 Ian Formanek
* $
* Beta Change History:
* 0 Tuborg 0.32 --/--/98 Petr Hamernik isReadOnly, rename methods added
* 0 Tuborg 0.33 --/--/98 Petr Hamernik getURL added
* 0 Tuborg 0.34 --/--/98 Jaroslav Tulach getURL made final
* 0 Tuborg 0.35 --/--/98 Petr Hamernik lock throws IOException
* 0 Tuborg 0.36 --/--/98 Jaroslav Tulach comments extended
* 0 Tuborg 0.38 --/--/98 Petr Hamernik file attributes
* 0 Tuborg 0.39 --/--/98 Petr Hamernik comments improved
* 0 Tuborg 0.40 --/--/98 Jan Formanek equals() and hashCode() added
* 0 Tuborg 0.41 --/--/98 Jaroslav Tulach late fireXYZ methods, only adds to the FS fire queue
* 0 Tuborg 0.42 --/--/98 Petr Hamernik URL protocol
* 0 Tuborg 0.43 --/--/98 Ales Novak NbfsURLConstants
*/